﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using SilverMindMap.Core.Model;
using SilverMindMap.Core.ViewModel;

namespace SilverMindMap.Core.View
{
    public partial class MindMapControl : UserControl, INodeSizeProvider
    {
        public static readonly TimeSpan AnimationDuration = TimeSpan.FromMilliseconds(600);

        public static DependencyProperty MindMapProperty = DependencyProperty.Register(
            "MindMap", typeof(MindMap), typeof(MindMapControl), new PropertyMetadata(null, OnMindMapChanged));

        public static DependencyProperty NodeTemplateProperty = DependencyProperty.Register(
            "NodeTemplate", typeof(DataTemplate), typeof(MindMapControl), new PropertyMetadata(null, OnNodeTemplateChanged));

        private static void OnMindMapChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            ((MindMapControl)sender).OnMindMapChanged((MindMap)args.OldValue, (MindMap)args.NewValue);
        }

        private static void OnNodeTemplateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            ((MindMapControl)sender).OnNodeTemplateChanged((DataTemplate)args.OldValue, (DataTemplate)args.NewValue);
        }

        private bool _layoutNeedsUpdate = false;
        private MindMapObserver _mindMapObserver = null;
        private INodeAnimator _animator;
        private CircleLayoutProvider _layoutProvider = null;
        private TranslateTransform _translateTransform;
        private readonly IDictionary<Node, NodeControl> _nodeControlMap;
        private readonly IDictionary<Link, LinkControl> _linkControlMap;
        private readonly CanvasPositionController _positionController;

        public MindMapControl()
        {
            _nodeControlMap = new Dictionary<Node, NodeControl>();
            _linkControlMap = new Dictionary<Link, LinkControl>();
            _animator = new SimpleNodeAnimator(this);
            _positionController = new CanvasPositionController(this);
            this.Loaded += (s, a) => RaiseNodeSizesInvalidated();
            this.SizeChanged += OnSizeChanged;

            InitializeComponent();

            
            _translateTransform = (TranslateTransform)this.Resources["_translateTransform"];
        }

        public MindMap MindMap
        {
            get { return (MindMap)GetValue(MindMapProperty); }
            set { SetValue(MindMapProperty, value); }
        }

        public DataTemplate NodeTemplate
        {
            get { return (DataTemplate)GetValue(NodeTemplateProperty); }
            set { SetValue(NodeTemplateProperty, value); }
        }

        private void OnSizeChanged(object sender, RoutedEventArgs args)
        {
            _rootTranslateTransform.X = this.ActualWidth / 2.0;
            _rootTranslateTransform.Y = this.ActualHeight / 2.0;
        }

        protected virtual void OnMindMapChanged(MindMap oldValue, MindMap newValue)
        {
            if (oldValue != null)
                DisconnectMindMap(oldValue);

            if (newValue != null)
                ConnectMindMap(newValue);
        }

        protected virtual void OnNodeTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
        {
            if (newValue == null)
            {
                foreach (var nodeControl in _nodeControlMap.Values)
                    nodeControl.ClearValue(NodeControl.NodeTemplateProperty);
            }
            else
            {
                foreach (var nodeControl in _nodeControlMap.Values)
                    nodeControl.NodeTemplate = newValue;
            }
        }

        private void ConnectMindMap(MindMap newMindMap)
        {
            _mindMapObserver = new MindMapObserver(newMindMap);
            _mindMapObserver.NodeAdded += (s, a) => RegisterNode(a.Node);
            _mindMapObserver.NodeRemoved += (s, a) => UnregisterNode(a.Node);
            _mindMapObserver.LinkAdded += (s, a) => RegisterLink(a.Link);
            _mindMapObserver.LinkRemoved += (s, a) => UnregisterLink(a.Link);

            foreach (var node in newMindMap.Nodes)
            {
                RegisterNode(node);

                foreach (var link in node.Links)
                    if (link.FirstNode == node)
                        RegisterLink(link);
            }

            _layoutProvider = new CircleLayoutProvider(newMindMap, this, _animator);
        }

        private void DisconnectMindMap(MindMap oldMindMap)
        {
            _mindMapObserver.Dispose();
            _mindMapObserver = null;
            
            _layoutProvider.Dispose();
            _layoutProvider = null;

            _nodeControlMap.Clear();
            _linkControlMap.Clear();
            _nodesPanel.Children.Clear();
            _linksPanel.Children.Clear();
        }
    
        private void RegisterNode(Node node)
        {
            var control = CreateControlForNode(node);
            control.Loaded += OnNodeControlLoaded;
            _nodesPanel.Children.Add(control);
            _nodeControlMap[node] = control;
            _layoutNeedsUpdate = true;
        }

        private void UnregisterNode(Node node)
        {
            var control = _nodeControlMap[node];
            _nodesPanel.Children.Remove(control);
            _nodeControlMap.Remove(node);
        }

        private void RegisterLink(Link link)
        {
            var control = CreateControlForLink(link);
            _linksPanel.Children.Add(control);
            _linkControlMap[link] = control;
        }

        private void UnregisterLink(Link link)
        {
            var control = _linkControlMap[link];
            _linksPanel.Children.Remove(control);
            _linkControlMap.Remove(link);
        }

        private void OnNodeControlLoaded(object sender, RoutedEventArgs args)
        {
            if (_layoutNeedsUpdate)
                RaiseNodeSizesInvalidated();

            _layoutNeedsUpdate = false;
        }

        protected virtual NodeControl CreateControlForNode(Node node)
        {
            var result = new NodeControl() 
            { 
                ViewModel = new NodeViewModel(node),
                BorderBrush = new SolidColorBrush(Colors.Black),
                BorderThickness = new Thickness(2),
                Background = new SolidColorBrush(Colors.Yellow)
            };

            if (this.NodeTemplate != null)
                result.NodeTemplate = this.NodeTemplate;

            return result;
        }

        protected virtual LinkControl CreateControlForLink(Link link)
        {
            return new LinkControl()
            {
                Link = link,
                BorderBrush = new SolidColorBrush(Colors.DarkGray),
                BorderThickness = new Thickness(2)
            };
        }

        public Size GetNodeSize(Node node)
        {
            var control = _nodeControlMap[node];
            if (control.DesiredSize.Width == 0.0 && control.DesiredSize.Height == 0.0)
                control.Measure(new Size(double.MaxValue, double.MaxValue));
            return control.DesiredSize;
        }

        protected virtual void RaiseNodeSizeChanged(Node node, Size newSize)
        {
            var handler = this.NodeSizeChanged;
            if (handler != null)
                handler(this, new NodeSizeChangedEventArgs(node, newSize));
        }

        public event EventHandler<NodeSizeChangedEventArgs> NodeSizeChanged;
        
        protected virtual void RaiseNodeSizesInvalidated()
        {
            var handler = this.NodesSizesInvalidated;
            if (handler != null)
                handler(this, new EventArgs());
        }

        public event EventHandler<EventArgs> NodesSizesInvalidated;

        private class SimpleNodeAnimator : INodeAnimator
        {
            private readonly MindMapControl _parentControl;
            private readonly IDictionary<Node, Storyboard> _nodeStoryboards;

            public SimpleNodeAnimator(MindMapControl parentControl)
            {
                _parentControl = parentControl;

                _nodeStoryboards = new Dictionary<Node, Storyboard>();
            }

            public void AnimateNodeTo(Node node, Point position)
            {
                var nodeControl = _parentControl._nodeControlMap[node];
                Storyboard existingStoryboard = null;
                _nodeStoryboards.TryGetValue(nodeControl.ViewModel.Node, out existingStoryboard);                    

                // Storyboard
                var newStoryboard = new Storyboard();
                newStoryboard.FillBehavior = FillBehavior.HoldEnd;
                _nodeStoryboards[nodeControl.ViewModel.Node] = newStoryboard;
                newStoryboard.Completed += (s, a) => _nodeStoryboards.Remove(nodeControl.ViewModel.Node);

                // Animation
                var nodePointAnimation = new PointAnimation();
                nodePointAnimation.Duration = new Duration(MindMapControl.AnimationDuration);
                nodePointAnimation.From = nodeControl.Position;
                nodePointAnimation.To = position;
                Storyboard.SetTarget(nodePointAnimation, nodeControl);
                Storyboard.SetTargetProperty(nodePointAnimation, new PropertyPath("Position"));                
                newStoryboard.Children.Add(nodePointAnimation);
                
                // All links
                foreach (var link in node.Links)
                {
                    LinkControl linkControl = null;
                    if (!_parentControl._linkControlMap.TryGetValue(link, out linkControl))
                        continue;

                    var linkPointAnimation = new PointAnimation();
                    linkPointAnimation.Duration = new Duration(MindMapControl.AnimationDuration);
                    linkPointAnimation.From = nodeControl.Position;
                    linkPointAnimation.To = position;
                    Storyboard.SetTarget(linkPointAnimation, linkControl);                    

                    if (link.FirstNode == node)
                        Storyboard.SetTargetProperty(linkPointAnimation, new PropertyPath("FirstPosition"));
                    else
                        Storyboard.SetTargetProperty(linkPointAnimation, new PropertyPath("SecondPosition"));

                    newStoryboard.Children.Add(linkPointAnimation);
                }

                if (existingStoryboard != null)
                    existingStoryboard.Stop();
                newStoryboard.Begin();
            }
        }

        private class CanvasPositionController
        {
            private readonly MindMapControl _parentControl;

            private bool _isMouseDown = false;
            private Point _previousPosition = new Point(0, 0);

            public CanvasPositionController(MindMapControl parentControl)
            {
                _parentControl = parentControl;

                _parentControl.MouseLeftButtonDown += OnMouseDown;
                _parentControl.MouseLeftButtonUp += OnMouseUp;
                _parentControl.MouseMove += OnMouseMove;
                _parentControl.LostMouseCapture += OnLostMouseCapture;

                _parentControl.Loaded += OnLoaded;
            }

            private void OnLoaded(object sender, RoutedEventArgs e)
            {                
            }

            private void OnLostMouseCapture(object sender, MouseEventArgs e)
            {
                _isMouseDown = false;
            }

            private void OnMouseMove(object sender, MouseEventArgs e)
            {
                if (!_isMouseDown)
                    return;

                var newPosition = e.GetPosition(_parentControl);
                double deltaX = _previousPosition.X - newPosition.X;
                double deltaY = _previousPosition.Y - newPosition.Y;

                _parentControl._translateTransform.X -= deltaX;
                _parentControl._translateTransform.Y -= deltaY;

                _previousPosition = newPosition;
            }

            private void OnMouseUp(object sender, MouseButtonEventArgs e)
            {
                _isMouseDown = false;
                _parentControl.ReleaseMouseCapture();
            }

            private void OnMouseDown(object sender, MouseButtonEventArgs e)
            {
                _isMouseDown = true;
                _parentControl.CaptureMouse();
                _previousPosition = e.GetPosition(_parentControl);                
            }
        }
    }
}
